#include <fcntl.h>
#include <limits.h>
#include "util/path_alloc_.c"
#include <dirent.h>
#include "apue.h"
/**
 * 示例阐述了 目录or文件 之间的关系，更重要的是介绍了遍历目录的工作流程
 * realloc 函数传入指向内存的指针不能被Free
 * realloc 函数扩充buff大小，其中的原内容保证相同
 */

typedef int Myfunc(const char *, const struct stat *, int );
static Myfunc myfunc;
static int  myftw(char *, Myfunc *);
static int  dopath(Myfunc *);
static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;

int main(int argc, char *argv[])
{
    int ret;

    if(argc != 2)
        err_quit("usage: ftw <starting-pathname>");

    ret = myftw(argv[1], myfunc);

    ntot = nreg + nblk + nchr + nfifo + nslink + nsock;

    if(ntot == 0)
        ntot = 1;
    
    printf("nreg files = %7ld, %5.2f %%\n", nreg, nreg*100.0/ntot);
    printf("ndir files = %7ld, %5.2f %%\n", ndir, ndir*100.0/ntot);
    printf("nblk files = %7ld, %5.2f %%\n", nblk, nblk*100.0/ntot);
    printf("nchr files = %7ld, %5.2f %%\n", nchr, nchr*100.0/ntot);
    printf("nfifo files = %7ld, %5.2f %%\n", nfifo, nfifo*100.0/ntot);
    printf("nslink files = %7ld, %5.2f %%\n", nslink, nslink*100.0/ntot);
    printf("nsock files = %7ld, %5.2f %%\n", nsock, nsock*100.0/ntot);
    return ret;
}

#define FTW_F 1
#define FTW_D 2
#define FTW_DNR 3
#define FTW_NS 4

static char *fullpath;
static size_t pathlen;

static int myftw(char *pathname, Myfunc *func){
    fullpath = path_alloc_(&pathlen);

    if(pathlen <= strlen(pathname)){
        pathlen = strlen(pathname) * 2;
        if((fullpath = realloc(fullpath, pathlen)) == NULL)
            err_sys("realloc failed");
    }

    strcpy(fullpath, pathname);
    return dopath(func);
}

static int dopath(Myfunc *func){
    struct stat statbuf;
    struct dirent *dirp;
    DIR *dp;
    int     ret, n;

    if(lstat(fullpath, &statbuf) < 0)
        return func(fullpath, &statbuf, FTW_NS); //无法获得相关信息

    if(S_ISDIR(statbuf.st_mode) == 0)
        return func(fullpath, &statbuf, FTW_F); //不是目录，是文件
    
    if((ret = func(fullpath, &statbuf, FTW_D)) != 0) //必定为0
        return ret;
    
    n = strlen(fullpath);
    
    if(n + NAME_MAX + 2 > pathlen){
        //确保pathname buffer 的大小可以再容纳一个文件 or 目录的名字
        pathlen *= 2;
        if((fullpath = realloc(fullpath, pathlen)) == NULL)
            err_sys("realloc failed");
    }

    fullpath[n++] = '/'; // "../pathname" -> "../pathname/"
    fullpath[n] = 0;
    if((dp = opendir(fullpath)) == NULL){
        return func(fullpath, &statbuf, FTW_DNR);
    }

    while((dirp = readdir(dp)) != NULL){
        if(strcmp(dirp->d_name, ".") == 0 ||
            strcmp(dirp->d_name, "..") == 0)
            continue;
        
        strcpy(&fullpath[n], dirp->d_name);

        if((ret = dopath(func)) != 0)
            break;
    }

    fullpath[n - 1] = 0;

    if(closedir(dp) < 0)
        err_ret("can't close directory %s", fullpath);

    return ret;        
}

static int myfunc(const char *pathname, const struct stat *statptr, int type){
    switch(type){
        case FTW_F:
            switch(statptr->st_mode & S_IFMT){
                case S_IFIFO: nfifo++; break;
                case S_IFCHR: nchr++; break;
                case S_IFBLK: nblk++; break;
                case S_IFREG: nreg++; break;
                case S_IFLNK: nslink++; break;
                case S_IFSOCK: nsock++; break;
                case S_IFDIR:
                    err_dump("for S_IFDIR for %s", pathname);
            }
            break;
        case FTW_D:
            ndir++;
            break;
        case FTW_NS:
            err_ret("stat error for %s", pathname);
            break;
        case FTW_DNR:
            err_ret("can't read directory %s", pathname);
            break;
        default:
            err_dump("unkonw type %d for pathname %s", type, pathname);
            break;
    }
    return 0;
}